package handler

import (
	"strings"

	"database/sql"

	"gitee.com/hp-huiw/family-expense-api/internal/constant"
	"gitee.com/hp-huiw/family-expense-api/internal/dto"
	"gitee.com/hp-huiw/family-expense-api/internal/service"
	"gitee.com/hp-huiw/my-go-core/util"
	"github.com/gin-gonic/gin"
)

type IncomeExpenseAnalysisHandler interface {
	QueryExistingDistinctYears(ctx *gin.Context)
	QuerySummaryByExistingYears(ctx *gin.Context)
	QueryOverallSummary(ctx *gin.Context)
	QueryAnnualMonthlySummary(ctx *gin.Context)
	QueryMonthlyDailySummary(ctx *gin.Context)
	QueryTopRankByAccountGroup(ctx *gin.Context)
	QuerySummaryByDetailType(ctx *gin.Context)
}

func NewIncomeExpenseAnalysisHandler(db *sql.DB) IncomeExpenseAnalysisHandler {
	return &incomeExpenseAnalysisHandler{
		incomeExpenseAnalysisService: service.NewIncomeExpenseAnalysisService(db),
	}
}

// Matches /query-existing-distinct-years of http get action.
func (h *incomeExpenseAnalysisHandler) QueryExistingDistinctYears(ctx *gin.Context) {
	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	result, err := h.incomeExpenseAnalysisService.QueryExistingDistinctYears(c, accountGroupID)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /query-summary-by-existing-years of http get action.
func (h *incomeExpenseAnalysisHandler) QuerySummaryByExistingYears(ctx *gin.Context) {
	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	years, err := h.incomeExpenseAnalysisService.QueryExistingDistinctYears(c, accountGroupID)

	if err != nil {
		ctx.Error(err)
		return
	}

	result, err := h.incomeExpenseAnalysisService.QueryAnnualSummary(c, accountGroupID, years)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /query-overall-summary of http get action.
func (h *incomeExpenseAnalysisHandler) QueryOverallSummary(ctx *gin.Context) {
	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	result, err := h.incomeExpenseAnalysisService.QueryOverallSummary(c, accountGroupID)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /query-annual-monthly-summary of http get action.
func (h *incomeExpenseAnalysisHandler) QueryAnnualMonthlySummary(ctx *gin.Context) {
	year, err := util.StringToInt(ctx.Query("year"))

	if err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	cashIncomeSummary, err := h.incomeExpenseAnalysisService.QueryAnnualMonthlySummary(c, accountGroupID, constant.IncomeExpenseTypeIncome, constant.PaymentTypeCash, year)

	if err != nil {
		ctx.Error(err)
		return
	}

	cashExpenseSummary, err := h.incomeExpenseAnalysisService.QueryAnnualMonthlySummary(c, accountGroupID, constant.IncomeExpenseTypeExpense, constant.PaymentTypeCash, year)

	if err != nil {
		ctx.Error(err)
		return
	}

	creditCardIncomeSummary, err := h.incomeExpenseAnalysisService.QueryAnnualMonthlySummary(c, accountGroupID, constant.IncomeExpenseTypeIncome, constant.PaymentTypeCreditCard, year)

	if err != nil {
		ctx.Error(err)
		return
	}

	creditCardExpenseSummary, err := h.incomeExpenseAnalysisService.QueryAnnualMonthlySummary(c, accountGroupID, constant.IncomeExpenseTypeExpense, constant.PaymentTypeCreditCard, year)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, map[string]any{
		"cashIncomeSummary":        cashIncomeSummary,
		"cashExpenseSummary":       cashExpenseSummary,
		"creditCardIncomeSummary":  creditCardIncomeSummary,
		"creditCardExpenseSummary": creditCardExpenseSummary,
	})
}

// Matches /query-monthly-daily-summary of http get action.
func (h *incomeExpenseAnalysisHandler) QueryMonthlyDailySummary(ctx *gin.Context) {
	year, err := util.StringToInt(ctx.Query("year"))

	if err != nil {
		ctx.Error(err)
		return
	}

	month, err := util.StringToInt(ctx.Query("month"))

	if err != nil {
		ctx.Error(err)
		return
	}

	c := ctx.Request.Context()
	accountGroupID := GetClaims(ctx).AccountGroupID
	cashIncomeSummary, err := h.incomeExpenseAnalysisService.QueryMonthlyDailySummary(c, accountGroupID, constant.IncomeExpenseTypeIncome, constant.PaymentTypeCash, year, month)

	if err != nil {
		ctx.Error(err)
		return
	}

	cashExpenseSummary, err := h.incomeExpenseAnalysisService.QueryMonthlyDailySummary(c, accountGroupID, constant.IncomeExpenseTypeExpense, constant.PaymentTypeCash, year, month)

	if err != nil {
		ctx.Error(err)
		return
	}

	creditCardIncomeSummary, err := h.incomeExpenseAnalysisService.QueryMonthlyDailySummary(c, accountGroupID, constant.IncomeExpenseTypeIncome, constant.PaymentTypeCreditCard, year, month)

	if err != nil {
		ctx.Error(err)
		return
	}

	creditCardExpenseSummary, err := h.incomeExpenseAnalysisService.QueryMonthlyDailySummary(c, accountGroupID, constant.IncomeExpenseTypeExpense, constant.PaymentTypeCreditCard, year, month)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, map[string]any{
		"cashIncomeSummary":        cashIncomeSummary,
		"cashExpenseSummary":       cashExpenseSummary,
		"creditCardIncomeSummary":  creditCardIncomeSummary,
		"creditCardExpenseSummary": creditCardExpenseSummary,
	})
}

// Matches /query-top-rank-by-account-group of http get action.
func (h *incomeExpenseAnalysisHandler) QueryTopRankByAccountGroup(ctx *gin.Context) {
	var params struct {
		PageSize    int64  `form:"pageSize"`
		PageIndex   int64  `form:"pageIndex"`
		Type        string `form:"type"`
		PaymentType string `form:"paymentType"`
		Year        string `form:"year"`
		Month       string `form:"month"`
	}

	if err := ctx.ShouldBindQuery(&params); err != nil {
		ctx.Error(err)
		return
	}

	var err error
	year := 0
	month := 0
	accountGroupID := GetClaims(ctx).AccountGroupID

	// params.Year might be null. Go int cannot be null. That's why extra action is needed.
	if strings.ToLower(params.Year) != "null" {
		year, err = util.StringToInt(params.Year)

		if err != nil {
			ctx.Error(err)
			return
		}
	}

	// params.Month might be null. Go int cannot be null. That's why extra action is needed.
	if strings.ToLower(params.Month) != "null" {
		month, err = util.StringToInt(params.Month)

		if err != nil {
			ctx.Error(err)
			return
		}
	}

	c := ctx.Request.Context()
	result, err := h.incomeExpenseAnalysisService.QueryTopRank(c, accountGroupID, params.PageSize, params.PageIndex, params.Type, params.PaymentType, year, month)

	if err != nil {
		ctx.Error(err)
		return
	}

	SendDataResponse(ctx, result)
}

// Matches /query-summary-by-detail-type of http get action.
func (h *incomeExpenseAnalysisHandler) QuerySummaryByDetailType(ctx *gin.Context) {
	var params struct {
		Type        string `form:"type"`
		PaymentType string `form:"paymentType"`
		Year        string `form:"year"`
		Month       string `form:"month"`
	}

	// If you see error in console which says:
	// [WARNING] Headers were already written. Wanted to override status code 400 with 200.
	// Tries to check the error of Bind func.
	//err := ctx.Bind(&params)
	if err := ctx.ShouldBindQuery(&params); err != nil {
		ctx.Error(err)
		return
	}

	accountGroupID := GetClaims(ctx).AccountGroupID
	var err error
	year := 0
	month := 0
	var result []dto.DetailTypeSummaryResponse

	// params.Year might be null. Go int cannot be null. That's why extra action is needed.
	if strings.ToLower(params.Year) != "null" {
		year, err = util.StringToInt(params.Year)

		if err != nil {
			ctx.Error(err)
			return
		}
	}

	// params.Month might be null. Go int cannot be null. That's why extra action is needed.
	if strings.ToLower(params.Month) != "null" {
		month, err = util.StringToInt(params.Month)

		if err != nil {
			ctx.Error(err)
			return
		}
	}

	c := ctx.Request.Context()

	if params.Type == constant.IncomeExpenseTypeExpense {
		result, err = h.incomeExpenseAnalysisService.QueryExpense(c, accountGroupID, params.PaymentType, year, month)

		if err != nil {
			ctx.Error(err)
			return
		}
	} else {
		result, err = h.incomeExpenseAnalysisService.QueryIncome(c, accountGroupID, params.PaymentType, year, month)

		if err != nil {
			ctx.Error(err)
			return
		}
	}

	SendDataResponse(ctx, result)
}

// Container of IncomeExpenseAnalysisHandler functions.
type incomeExpenseAnalysisHandler struct {
	incomeExpenseAnalysisService service.IncomeExpenseAnalysisService
}
